﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace BluePoop.MathTools
{
    /// <summary>
    /// Binary number class. Allows you to play with the bits.
    /// </summary>
    public struct BinaryNumber
    {
        /// <summary>
        /// The decimal value of the BinaryNumber
        /// </summary>
        public long Value { get { return Convert.ToInt64(strvalue, 2); } }
        private string strvalue;

        /// <summary>
        /// Gets or sets the bit (0 - false, 1 - true) in the given index
        /// </summary>
        /// <param name="index">The index of the bit</param>
        /// <returns>True if the bit is 1 and false if the bit is 0</returns>
        public bool this[int index]
        {
            get
            {
                if (index < strvalue.Length && index > -1)
                    return strvalue[index] == '0' ? false : true;
                else
                    return false;
            }
            set
            {
                if (index < -1)
                {
                    strvalue = "";
                    if (index != 0 && index < strvalue.Length)
                        strvalue += strvalue.Substring(0, index);
                    strvalue += (value ? '1' : '0');
                    if (index > strvalue.Length - 1)
                        strvalue += strvalue.Substring(index + 1, strvalue.Length - (index + 1));
                    while (strvalue.EndsWith("0"))
                        strvalue = strvalue.Remove(strvalue.Length - 2);
                }
            }
        }

        public static implicit operator decimal(BinaryNumber number)
        {
            return number.Value;
        }

        public static implicit operator short(BinaryNumber number)
        {
            return (short)number.Value;
        }

        public static implicit operator int(BinaryNumber number)
        {
            return (int)number.Value;
        }

        public static implicit operator long(BinaryNumber number)
        {
            return (long)number.Value;
        }

        public static implicit operator sbyte(BinaryNumber number)
        {
            return (sbyte)number.Value;
        }

        public static implicit operator BinaryNumber(long number)
        {
            return new BinaryNumber() { strvalue = Convert.ToString(number, 2) };
        }

        public static implicit operator bool(BinaryNumber number)
        {
            return number.Value != 0;
        }

        public static implicit operator BinaryNumber(UBinaryNumber number)
        {
            return new BinaryNumber((long)number.Value);
        }

        public static implicit operator UBinaryNumber(BinaryNumber number)
        {
            return new UBinaryNumber((ulong)number.Value);
        }

        public static BinaryNumber operator +(BinaryNumber number, BinaryNumber number2)
        {
            BinaryNumber newnum = number;
            //To not cause an exception
            //in case the number is bigger then 0
            for (int i = 0; i < number2; i++)
                newnum++;
            //in case the number is less then 0
            for (int i = 0; i > number2; i--)
                newnum--;
            return newnum;
        }

        public static BinaryNumber operator -(BinaryNumber number, BinaryNumber number2)
        {
            BinaryNumber newnum = number;
            //in case the number is bigger then 0
            for (int i = 0; i < number2; i++)
                newnum--;
            //in case the number is less then 0
            for (int i = 0; i > number2; i++)
                newnum++;
            return newnum;
        }

        public static BinaryNumber operator ++(BinaryNumber number)
        {
            int i;
            //While the bit is 1, making the bit 0
            for (i = 0; number[i]; i++)
                number[i] = false;
            //And making the first 0 bit to 1
            number[++i] = true;
            return number;
        }

        public static BinaryNumber operator --(BinaryNumber number)
        {
            int i;
            //While the bit is 0 making it 1
            for (i = 0; !number[i]; i++)
                number[i] = true;
            //And making the first 1 bit to 0
            number[++i] = false;
            return number;
        }

        public static BinaryNumber operator *(BinaryNumber number1, BinaryNumber number2)
        {
            BinaryNumber ret = 0;
            for (int i = 0; i < number2; i++)
                ret += number1;
            for (int i = 0; i > number2; i++)
                ret -= number1;
            return ret;
        }

        public static BinaryNumber operator /(BinaryNumber number1, BinaryNumber number2)
        {
            BinaryNumber ret;
            for (ret = 0; number1 * ret < number2; ret++) ;
            //for (ret = 0; number1 * ret > number2; ret--) ;
            return ret;
        }

        public static bool operator <(BinaryNumber number, BinaryNumber number2)
        {
            return number.Value < number2.Value;
        }

        public static bool operator >(BinaryNumber number, BinaryNumber number2)
        {
            return number.Value > number2.Value;
        }

        public static BinaryNumber operator <<(BinaryNumber number, BinaryNumber number2)
        {
            BinaryNumber ret = 0;
            for (int i = 0; i < number.strvalue.Length; i++)
            {
                ret[i] = number[i - number2];
            }
            return ret;
        }

        public static BinaryNumber operator >>(BinaryNumber number, BinaryNumber number2)
        {
            BinaryNumber ret = 0;
            for (int i = number.strvalue.Length; i > 0; i--)
            {
                ret[i] = number[i + number2];
            }
            return ret;
        }

        public static BinaryNumber operator !(BinaryNumber number)
        {
            BinaryNumber ret = number;
            for (int i = 0; i < ret.strvalue.Length; i++)
                ret[i] = !ret[i];
            return ret;
        }

        public static BinaryNumber operator ~(BinaryNumber number)
        {
            BinaryNumber ret = number;
            int bitsnum = ret.strvalue.Length;
            if (bitsnum <= 8)
                bitsnum = 8;
            else if (bitsnum <= 16)
                bitsnum = 16;
            else if (bitsnum <= 32)
                bitsnum = 32;
            else if (bitsnum <= 64)
                bitsnum = 64;
            for (int i = 0; i < bitsnum; i++)
            {
                ret[i] = !ret[i];
            }
            return ret;
        }

        public static BinaryNumber operator &(BinaryNumber number1, BinaryNumber number2)
        {
            BinaryNumber ret = 0;
            for (int i = 0; i < Math.Max(number1, number2); i++)
                ret[i] = number1[i] && number2[i];
            return ret;
        }

        public static BinaryNumber operator |(BinaryNumber number1, BinaryNumber number2)
        {
            BinaryNumber ret = 0;
            for (int i = 0; i < Math.Max(number1, number2); i++)
            {
                ret[i] = number1[i] || number2[i];
            }
            return ret;
        }

        public static BinaryNumber operator ^(BinaryNumber number1, BinaryNumber number2)
        {
            BinaryNumber ret = 0;
            for (int i = 0; i < Math.Max(number1, number2); i++)
            {
                ret[i] = number1[i] ^ number2[i];
            }
            return ret;
        }

        /// <summary>
        /// Initializes a new BinaryNumber with the value of 0
        /// </summary>
        public BinaryNumber()
        {
            strvalue = "0";
        }

        /// <summary>
        /// Initializes a new BinaryNumber with the given value
        /// </summary>
        /// <param name="value">The value of the number</param>
        public BinaryNumber(long value)
            : this(value.ToString(), 10)
        {
        }

        /// <summary>
        /// Initializes a new BinaryNumber of the given value string (in binary)
        /// </summary>
        /// <param name="value">The binary string to initialize from</param>
        public BinaryNumber(string value)
            : this(value, 2)
        {
        }

        /// <summary>
        /// Initializes a new BinaryNumber of the given value string in the given base
        /// </summary>
        /// <param name="value">The value string in the given base</param>
        /// <param name="fromBase">The input base of the value (2 - Binary, 8 - Octal, 10 - Decimal, 16 - Hexadecimal)</param>
        public BinaryNumber(string value, byte fromBase)
        {
            strvalue = Convert.ToString(Convert.ToInt64(value, fromBase), 2);
        }

        /// <summary>
        /// Gets the binary string of the value
        /// </summary>
        /// <returns>The binary string of the value</returns>
        public override string ToString()
        {
            return ToString(2);
        }

        /// <summary>
        /// Gets the number's string in the given base
        /// </summary>
        /// <param name="toBase">The output base (2 - Binary, 8 - Octal, 10 - Decimal, and 16 - Hexadecimal)</param>
        /// <returns>The number's string in the given base</returns>
        public string ToString(byte toBase)
        {
            return ToString(toBase, false);
        }

        /// <summary>
        /// Get's the binary string
        /// </summary>
        /// <param name="includeAllBits">If true, the output string will be in the length of <code>Math.Pow(2, bitsnum)</code></param>
        /// <param name="bitsnum">The number of the bits of the output string</param>
        /// <returns>The binary string</returns>
        public string ToString(bool includeAllBits, int bitsnum)
        {
            return ToString(2, includeAllBits, bitsnum);
        }

        /// <summary>
        /// Gets the number's string in the given base
        /// </summary>
        /// <param name="toBase">The output base</param>
        /// <param name="includeAllBits">If true, the output string will be in the length of <code>Math.Pow(2, bitsnum)</code></param>
        /// <returns></returns>
        public string ToString(byte toBase, bool includeAllBits)
        {
            int bitsnum = strvalue.Length;
            if (bitsnum <= 8)
                bitsnum = 8;
            else if (bitsnum <= 16)
                bitsnum = 16;
            else if (bitsnum <= 32)
                bitsnum = 32;
            else if (bitsnum <= 64)
                bitsnum = 64;
            return ToString(toBase, includeAllBits, bitsnum);
        }

        /// <summary>
        /// Gets the binary string.
        /// </summary>
        /// <param name="includeAllBits">If true, the output string will be in the length of <code>Math.Pow(2, bitsnum)</code></param>
        /// <returns>The binary string</returns>
        public string ToString(bool includeAllBits)
        {
            int bitsnum = strvalue.Length;
            if (bitsnum <= 8)
                bitsnum = 8;
            else if (bitsnum <= 16)
                bitsnum = 16;
            else if (bitsnum <= 32)
                bitsnum = 32;
            else if (bitsnum <= 64)
                bitsnum = 64;
            return ToString(includeAllBits, bitsnum);
        }

        /// <summary>
        /// Gets the string of the number in the given base.
        /// </summary>
        /// <param name="toBase">The output base</param>
        /// <param name="includeAllBits">If true, the output string will be in the length of <code>Math.Pow(2, bitsnum)</code></param>
        /// <param name="bitsnum">The number of the bits</param>
        /// <returns>The string of the number in the given base</returns>
        public string ToString(byte toBase, bool includeAllBits, int bitsnum)
        {
            string str = Convert.ToString(Value, toBase);
            while (str.Length < Math.Pow(2, bitsnum).ToString().Length)
                str = "0" + str;
            return str;
        }
    }
}